#import "draw.h"

/* Optimally viewed in a wide window.  Make your window big enough so that this comment fits entirely on one line w/o wrapping. */

#define GROUP_CACHE_THRESHOLD 4

@implementation Group : Graphic
/*
 * This Graphic is used to create heirarchical groups of other Graphics.
 * It simply keeps a list of all the Graphics in the group and resizes
 * and translates them as the Group object itself is resized and moved.
 * It also passes messages sent to the Group onto its members.
 *
 * For efficiency, we cache the group whenever it passes the caching
 * threshold.  Thus, grouping becomes a tool to let the user have some
 * control over the memory/speed tradeoff (which can be different
 * depending on the kind of drawing the user is making).
 */

/* Factory method */

+ initialize
/*
 * This bumps the class version so that we can compatibly read
 * old Graphic objects out of an archive.
 */
{
    [Group setVersion:3];
    return self;
}

/* Initialization */

- initList:(List *)list
/*
 * Creates a new grouping with list containing the list of Graphics
 * in the group.  Groups of Groups is perfectly allowable.  We have
 * to keep track of the largest linewidth in the group as well as
 * whether any of the elements of the group have arrows since both
 * of those attributes affect the extended bounds of the Group.
 * We set any objects which might be cacheing (notably subgroups of
 * this group) to be not cacheable since it is no use for them to
 * cache themselves when we are caching them as well.  We also have
 * to check to see if there are any TextGraphic's in the group
 * because we can't cache ourselves if there are (unfortunately).
 */
{
    int i;
    NXRect r;
    Graphic *graphic;

    [super init];

    gFlags.mightBeLinked = YES;
    i = [list count];
    graphic = [list objectAt:--i];
    [graphic getBounds:&bounds];
    gFlags.arrow = [graphic lineArrow];
    linewidth = [graphic lineWidth];
    bounds.size.width = MAX(1.0, bounds.size.width);
    bounds.size.height = MAX(1.0, bounds.size.height);
    while (i) {
	graphic = [list objectAt:--i];
	[graphic getBounds:&r];
	[graphic setCacheable:NO];
	r.size.width = MAX(1.0, r.size.width);
	r.size.height = MAX(1.0, r.size.height);
	NXUnionRect(&r, &bounds);
	if (!gFlags.arrow && [graphic lineArrow]) gFlags.arrow = [graphic lineArrow];
	if ([graphic lineWidth] > linewidth) linewidth = [graphic lineWidth];
	if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) hasTextGraphic = YES;
    }

    components = list;
    lastRect = bounds;

    return self;
}

- free
{
    [components freeObjects];
    [components free];
    [cache free];
    return [super free];
}

/* Public methods */

- transferSubGraphicsTo:(List *)list at:(int)position
/*
 * Called by Ungroup.  This just unloads the components into the
 * passed list, modifying the bounds of each of the Graphics
 * accordingly (remember that when a Graphic joins a Group, its
 * bounds are still kept in GraphicView coordinates (not
 * Group-relative coordinates), but they may be non-integral,
 * we can't allow non-integral bounds outside a group because
 * it conflicts with the compositing rules (and we use
 * compositing to move graphics around).
 */
{
    int i, count;
    Graphic *graphic;
    NXRect gbounds;
    BOOL zeroWidth, zeroHeight;

    count = [components count];
    for (i = (count - 1); i >= 0; i--) {
	graphic = [components objectAt:i];
	[graphic getBounds:&gbounds];
	if (!gbounds.size.width) {
	    zeroWidth = YES;
	    gbounds.size.width = 1.0;
	} else zeroWidth = NO;
	if (!gbounds.size.height) {
	    zeroHeight = YES;
	    gbounds.size.height = 1.0;
	} else zeroHeight = NO;
	NXIntegralRect(&gbounds);
	if (zeroWidth) gbounds.size.width = 0.0;
	if (zeroHeight) gbounds.size.height = 0.0;
	[graphic setBounds:&gbounds];
	[graphic setCacheable:YES];
	[list insertObject:graphic at:position];
    }

    return self;
}

- (List *)subGraphics
{
    return components;
}

/* Group must override all the setting routines to forward to components */

- makeGraphicsPerform:(SEL)aSelector with:(const void *)anArgument
{
    [components makeObjectsPerform:aSelector with:(id)anArgument];
    [cache free];
    cache = nil;
    return self;
}

- changeFont:sender
{
    return [self makeGraphicsPerform:@selector(changeFont:) with:sender];
}

- (Font *)font
{
    int i;
    Font *gfont, *font = nil;

    i = [components count];
    while (i--) {
	gfont = [[components objectAt:i] font];
	if (gfont) {
	    if (font && font != gfont) {
		font = nil;
		break;
	    } else {
		font = gfont;
	    }
	}
    }

    return font;
}

- setLineWidth:(const float *)value
{
    return [self makeGraphicsPerform:@selector(setLineWidth:) with:value];
}

- setGray:(const float *)value
{
    return [self makeGraphicsPerform:@selector(setGray:) with:value];
}

- setFillColor:(NXColor *)aColor
{
    return [self makeGraphicsPerform:@selector(setFillColor:) with:aColor];
}

- setFill:(int)mode
{
    return [self makeGraphicsPerform:@selector(setFill:) with:(void *)mode];
}

- setLineColor:(NXColor *)aColor
{
    return [self makeGraphicsPerform:@selector(setLineColor:) with:aColor];
}

- setLineCap:(int)value
{
    return [self makeGraphicsPerform:@selector(setLineCap:) with:(void *)value];
}

- setLineArrow:(int)value
{
    return [self makeGraphicsPerform:@selector(setLineArrow:) with:(void *)value];
}

- setLineJoin:(int)value
{
    return [self makeGraphicsPerform:@selector(setLineJoin:) with:(void *)value];
}

/* Link methods */

/*
 * Called after unarchiving and after a linkManager has been created for
 * the document this Graphic is in.  Graphic's implementation of this just
 * adds the link to the linkManager.
 */

- reviveLink:(NXDataLinkManager *)linkManager
{
    [components makeObjectsPerform:@selector(reviveLink:) with:linkManager];
    return self;
}

/*
 * This returns self if there is more than one linked Graphic in the Group.
 * If aLink is not nil, returns the Graphic which is linked by that link.
 * If aLink is nil, then it returns the one and only linked Graphic in the
 * group or nil otherwise.  Used when updating the link panel and when
 * redrawing link outlines.
 */

- (Graphic *)graphicLinkedBy:(NXDataLink *)aLink
{
    int i, linkCount = 0;
    Graphic *graphic = nil;

    for (i = [components count]-1; i >= 0; i--) {
	if (graphic = [[components objectAt:i] graphicLinkedBy:aLink]) {
	    if ([graphic isKindOf:[Group class]]) return graphic;
	    linkCount++;
	}
    }

    return (linkCount <= 1) ? graphic : self;
}

/*
 * When you copy/paste a Graphic, its identifier must be reset to something
 * different since you don't want the pasted one to have the same identifier
 * as the copied one!  See gvPasteboard.m.
 */

- resetIdentifier
{
    [components makeObjectsPerform:@selector(resetIdentifier)];
    return self;
}

/*
 * Used when creating an NXSelection representing all the Graphics
 * in a selection.  Has to recurse through Groups because you still
 * want the NXSelection to be valid even if the Graphics are ungrouped
 * in the interim between the time the selection is determined to the
 * time the links stuff asks questions about the selection later.
 */

- writeIdentifierTo:(char *)buffer
{
    int i = [components count];
    char *s = buffer;

    if (i) {
	[[components objectAt:--i] writeIdentifierTo:s];
	s += strlen(s);
	while (i--) {
	    *s++ = ' ';
	    [[components objectAt:i] writeIdentifierTo:s];
	    s += strlen(s);
	}
    }

    return self;
}

/*
 * This is used by the links stuff to allocate a buffer big enough to
 * put all the identifiers for all the graphics in this Group into.
 */

- (int)graphicCount
{
    int count = 0, i = [components count];
    while (i--) count += [[components objectAt:i] graphicCount];
    return count;
}

/*
 * See the method findGraphicInSelection: in gvLinks.m to see how this
 * method is used (it basically just lets you get back to a Graphic
 * from its identifier whether its in a Group or not).
 */

- (Graphic *)graphicIdentifiedBy:(int)anIdentifier
{
    int i = [components count];
    while (i--) {
	Graphic *graphic = [components objectAt:i];
	if (graphic = [graphic graphicIdentifiedBy:anIdentifier]) return graphic;
    }
    return nil;
}

/*
 * We pass this method onto all the things inside the group since
 * there might be linked things inside the group.
 */

- readLinkFromPasteboard:(Pasteboard *)pboard usingManager:(NXDataLinkManager *)linkManager useNewIdentifier:(BOOL)useNewIdentifier
{
    int i = [components count];
    while (i--) {
	Graphic *graphic = [components objectAt:i];
	if ([graphic mightBeLinked]) [graphic readLinkFromPasteboard:pboard usingManager:linkManager useNewIdentifier:useNewIdentifier];
    }
    return nil;
}

/* Form Entry methods.  See TextGraphic.m for details. */

- (BOOL)hasFormEntries
{
    int i = [components count];
    while (i--) if ([[components objectAt:i] hasFormEntries]) return YES;
    return NO;
}

- (BOOL)writeFormEntryToStream:(NXStream *)stream
{
    BOOL retval = NO;
    int i = [components count];
    while (i--) if ([[components objectAt:i] writeFormEntryToStream:stream]) retval = YES;
    return retval;
}

/* Notification methods */

- wasRemovedFrom:(GraphicView *)sender
{
    [components makeObjectsPerform:@selector(wasRemovedFrom:) with:sender];
    [cache free];
    cache = nil;
    return self;
}

- wasAddedTo:(GraphicView *)sender
{
    [components makeObjectsPerform:@selector(wasAddedTo:) with:sender];
    return self;
}

/* Color drag-and-drop support. */

- (Graphic *)colorAcceptorAt:(const NXPoint *)point
{
    int i, count;
    Graphic *graphic;

    count = [components count];
    for (i = 0; i < count; i++) {
	if (graphic = [[components objectAt:i] colorAcceptorAt:point]) return graphic;
    }

    return nil;
}

/* We can't cache ourselves if we have a TextGraphic in the Group. */

- (BOOL)hasTextGraphic
{
    return hasTextGraphic;
}

- setCacheable:(BOOL)flag
/*
 * Sets whether we do caching of this Group or not.
 */
{
    dontCache = flag ? NO : YES;
    if (dontCache) {
	[cache free];
	cache = nil;
    }
    return self;
}

- (BOOL)isCacheable
{
    return !hasTextGraphic && !dontCache;
}

- draw
/*
 * Individually scales and translates each Graphic in the group and draws
 * them.  This is done this way so that ungrouping is trivial.  Note that
 * if we are caching, we need to take the extra step of translating
 * everything to the origin, drawing them in the cache, then translating
 * them back.
 */
{
    int i;
    Graphic *g;
    NXRect eb, b;
    float sx = 1.0, sy = 1.0, tx, ty;
    BOOL changed, changedSize, caching = NO;

    if (bounds.size.width < 1.0 || bounds.size.height < 1.0 || !components) return self;

    changedSize = lastRect.size.width != bounds.size.width || lastRect.size.height != bounds.size.height;
    changed = changedSize || lastRect.origin.x != bounds.origin.x || lastRect.origin.y != bounds.origin.y;

    if ((changedSize || !cache) && NXDrawingStatus == NX_DRAWING) {
	[cache free];
	cache = nil;
	if (DrawStatus != Resizing && [self isCacheable] && [components count] > GROUP_CACHE_THRESHOLD) {
	    caching = YES;
	    [self getExtendedBounds:&eb];
	    cache = [[NXImage allocFromZone:[self zone]] initSize:&eb.size];
	    [cache lockFocus];
	    [[[NXApp focusView] window] reenableDisplay];	/* workaround for AppKit bug? */
	    PStranslate(- eb.origin.x, - eb.origin.y);
	    PSsetalpha(0.0);
	    PSsetgray(NX_WHITE);
	    NXRectFill(&eb);
	    PSsetalpha(1.0);
	}
    }

    if (changedSize) {
	sx = bounds.size.width / lastRect.size.width;
	sy = bounds.size.height / lastRect.size.height;
    }

    i = [components count];
    while (i) {
	g = [components objectAt:--i];
	if (changed) {
	    [g getBounds:&b];
	    tx = (bounds.origin.x + ((b.origin.x - lastRect.origin.x) / lastRect.size.width * bounds.size.width)) - b.origin.x;
	    ty = (bounds.origin.y + ((b.origin.y - lastRect.origin.y) / lastRect.size.height * bounds.size.height)) - b.origin.y;
	    b.origin.x = b.origin.x + tx;
	    b.origin.y = b.origin.y + ty;
	    b.size.width = b.size.width * sx;
	    b.size.height = b.size.height * sy;
	    [g setBounds:&b];
	}
	if (NXDrawingStatus != NX_DRAWING || !cache || caching) {
	    [g setGraphicsState];	/* does a gsave ... */
	    [g draw];
	    PSgrestore();		/* ... so we need this grestore */
	}
    }

    if (cache && NXDrawingStatus == NX_DRAWING) {
	if (caching) {
	    [cache unlockFocus];
	} else {
	    [self getExtendedBounds:&eb];
	}
	[cache composite:NX_SOVER toPoint:&eb.origin];
    }

    lastRect = bounds;

    return self;
}

- (BOOL)hit:(const NXPoint *)point
/*
 * Gets a hit if any of the items in the group gets a hit.
 */
{
    int i;
    NXPoint p;
    float px, py;
    Graphic *graphic;

    if ([super hit:point]) {
	if (components) {
	    p = *point;
	    px = (p.x - bounds.origin.x) / bounds.size.width;
	    p.x = px * lastRect.size.width + lastRect.origin.x;
	    py = (p.y - bounds.origin.y) / bounds.size.height;
	    p.y = py * lastRect.size.height + lastRect.origin.y;
	    i = [components count];
	    while (i) {
		graphic = [components objectAt:--i];
		if ([graphic hit:&p]) return YES;
	    }
	} else {
	    return YES;
	}
    }

    return NO;
}

/* Compatibility methods */

- replaceWithImage
/*
 * Since we got rid of Tiff and PSGraphic and replaced them
 * with the unified Image graphic, we need to go through our
 * list and replace all of them with an Image graphic.
 */
{
    int i;
    Graphic *graphic, *newGraphic;

    for (i = [components count]-1; i >= 0; i--) {
	graphic = [components objectAt:i];
	newGraphic = [graphic replaceWithImage];
	if (graphic != newGraphic) {
	    if (graphic) {
		[components replaceObjectAt:i with:newGraphic];
	    } else {
		[components removeObjectAt:i];
	    }
	}
    }

    return self;
}

/* Archiving methods */

- write:(NXTypedStream *)stream
/*
 * Just writes out the components.
 */
{
    [super write:stream];
    NXWriteTypes(stream, "@", &components);
    NXWriteType(stream, "c", &dontCache);
    NXWriteRect(stream, &lastRect);
    NXWriteType(stream, "c", &hasTextGraphic);
    return self;
}

static BOOL checkForTextGraphic(List *list)
{
    int i;
    Graphic *graphic;

    for (i = [list count]-1; i >= 0; i--) {
	graphic = [list objectAt:i];
	if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) return YES;
    }

    return NO;
}

- read:(NXTypedStream *)stream
{
    [super read:stream];
    NXReadTypes(stream, "@", &components);
    lastRect = bounds;
    if (NXTypedStreamClassVersion(stream, "Group") > 1) {
	NXReadType(stream, "c", &dontCache);
	NXReadRect(stream, &lastRect);
    }
    if (NXTypedStreamClassVersion(stream, "Group") > 2) {
	NXReadType(stream, "c", &hasTextGraphic);
    } else {
	hasTextGraphic = checkForTextGraphic(components);
    }
    return self;
}

@end

